﻿using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;

using GroupChallenge.Web.Models.Shared;

namespace GroupChallenge.Web
{
    public class GameManager
    {
        #region Private Members

        #endregion                              // Private Members

        #region Private Members

        private object _AnswerFirstLock = new object();         // Lock for the single-first winner.
        private Dictionary<string, Player> _Players = new Dictionary<string, Player>(50);
        private Dictionary<int, Question> _Questions = new Dictionary<int, Question>(50);

        #endregion                              // Private Members

        #region Properties

        private Game _CurrentGame = new Game();
        public Game CurrentGame
        {
            get { return _CurrentGame; }
            set { _CurrentGame = value; }
        }

        public string DataPath { get; set; }
        public string PlayerImagePath { get; set; }
        public string PlayerImagePathUrl { get; set; }
        public string VirtualRootUrl { get; set; }

        public Question CurrentQuestion
        {
            get
            {
                Question ret = null;

                if (_Questions.Count > 0 &&
                    _Questions.ContainsKey(CurrentGame.CurrentQuestionId))
                {
                    ret = _Questions[CurrentGame.CurrentQuestionId];
                }

                return ret;
            }
        }

        #endregion                              // Properties

        #region Private Methods

        private object _GameChangeLock = new object();

        private void GameChanged()
        {
            // Avoid too many updates at once.
            lock (_GameChangeLock)
            {
                _CurrentGame.LastChangeTime = DateTime.Now;
            }
        }

        private bool PlayerImageExists(int playerId)
        {
            string imageSourceUrl = string.Format(@"{0}\Player_{1}.jpg", Global.GameMgr.PlayerImagePath, playerId);

            return File.Exists(imageSourceUrl);
        }

        private FriendlyResult AddNewQuestionToPool(Question newQuestion)
        {
            FriendlyResult ret = null;

            if (null == newQuestion ||
                string.IsNullOrEmpty(newQuestion.QuestionText) ||
                string.IsNullOrEmpty(newQuestion.QuestionText.Trim()))
            {
                ret = new FriendlyResult() { ResultId = 0, FriendlyText = "Missing the question text." };
            }
            else
            {
                // The trimmed question to hash for a unique key.
                string trimmedQuestion = newQuestion.QuestionText.Trim();

                newQuestion.QuestionId = newQuestion.QuestionText.Trim().GetHashCode();

                if (_Questions.ContainsKey(newQuestion.QuestionId))
                {
                    ret = new FriendlyResult() { ResultId = 0, FriendlyText = "Question already exists." };
                }
                else
                {
                    _Questions.Add(newQuestion.QuestionId, newQuestion);

                    ret = new FriendlyResult() { ResultId = 1, FriendlyText = "Successfully submitted the question." };
                }
            }

            return ret;
        }

        #endregion                              // Private Methods

        #region Internal Client Methods - Game

        // 0-Unknown, 1-Not Playing, 2-No Change, 3-Changed, 4-Game Over
        internal int GetCurrentGameState(ref DateTime lastChangeTime, out Game currentGame, out Question currentQuestion)
        {
            int ret = 0;
            currentGame = null;
            currentQuestion = null;

            if (null != _CurrentGame)
            {
                if (_Players.Count < 1)
                {
                    ret = 4;
                }
                else if (lastChangeTime != DateTime.MinValue &&
                    lastChangeTime == _CurrentGame.LastChangeTime)
                {
                    ret = 2;
                }
                else                            // Different time - changes occured.
                {
                    // Time difference since last request.  Game change or game over?  Still return game state.
                    lastChangeTime = _CurrentGame.LastChangeTime.Value;

                    // Copy the game to return.
                    currentGame = _CurrentGame;

                    if (_CurrentGame.EndTime > DateTime.MinValue)
                    {
                        ret = 4;
                    }
                    else
                    {
                        ret = 3;

                        currentQuestion = CurrentQuestion;
                    }
                }
            }

            return ret;
        }

        internal FriendlyResult ResetGame()
        {
            FriendlyResult ret = new FriendlyResult() { FriendlyText = "Reset the game." };

            // Return the last high score if any.
            if (CurrentGame.HighScore > 0 &&
                !string.IsNullOrEmpty(CurrentGame.HighScorePlayerNickName))
            {
                ret.FriendlyText += string.Format("  Last high score was {0} with {1}",
                    CurrentGame.HighScorePlayerNickName, CurrentGame.HighScore);
            }

            ClearScores();

            CurrentGame.StartTime = DateTime.Now;
            CurrentGame.EndTime = DateTime.MinValue;
            CurrentGame.LastChangeTime = DateTime.MinValue;
            CurrentGame.CurrentQuestionId = 0;
            CurrentGame.HighScore = 0;
            CurrentGame.HighScorePlayerNickName = string.Empty;

            int questionsLoaded = LoadQuestions();
            ret.FriendlyText += string.Format("  Loaded {0} questions.", questionsLoaded.ToString());

            GameChanged();

            return ret;
        }

        private void ClearScores()
        {
            if (null != _Players &&
                _Players.Count > 0)
            {
                Dictionary<string, Player>.Enumerator enumer = _Players.GetEnumerator();
                while (enumer.MoveNext())
                {
                    enumer.Current.Value.CurrentScore = 0;
                }
            }
        }

        internal FriendlyResult StopGame()
        {
            FriendlyResult ret = new FriendlyResult();

            if (CurrentGame.HighScore > 0 &&
                !string.IsNullOrEmpty(CurrentGame.HighScorePlayerNickName))
            {
                ret.FriendlyText = string.Format("  Game winner is {0} with a score of {1}.",
                    CurrentGame.HighScorePlayerNickName, CurrentGame.HighScore);
            }
            else
            {
                ret.FriendlyText = "Game stopped.  No scores available.";
            }


            CurrentGame.EndTime = DateTime.Now;
            CurrentGame.LastChangeTime = CurrentGame.EndTime;
            CurrentGame.CurrentQuestionId = 0;

            GameChanged();

            return ret;
        }

        #endregion                              // Internal Client Methods - Game

        #region Internal Client Methods - Players

        // 0-Unknown, 1-Success, 2-Validation Error, 3-Nickname already used, 4-Player Id Not Valid
        internal FriendlyResult JoinGame(string nickName, string fullName, int playerId)
        {
            FriendlyResult ret = new FriendlyResult();

            Player newPlayer = new Player() { NickName = nickName, FullName = fullName, PlayerId = playerId };

            // Get error message if invalid.
            ret.FriendlyText = newPlayer.Validate();

            // Validated the player - No error.
            if (!string.IsNullOrEmpty(ret.FriendlyText))
            {
                ret.ResultId = 2;
            }
            else
            {
                if (_Players.ContainsKey(newPlayer.LoweredNickName))
                {
                    ret.FriendlyText = "Nickname already used.\r\nPlease try another.";
                    ret.ResultId = 3;
                }
                else if (!PlayerImageExists(playerId))
                {
                    ret.FriendlyText = "Player Id Not Valid.  No player picture uploaded yet.";
                    ret.ResultId = 4;
                }
                else
                {
                    _Players.Add(newPlayer.LoweredNickName, newPlayer);

                    ret.FriendlyText = "You have joined the game!!!";
                    ret.ResultId = 1;
                }
            }

            return ret;
        }

        internal bool LeaveGame(string nickName)
        {
            if (!string.IsNullOrEmpty(nickName))
            {
                string loweredNickName = nickName.Trim().ToLowerInvariant();

                // Remove player.
                if (_Players.ContainsKey(loweredNickName))
                {
                    _Players.Remove(loweredNickName);
                }
            }

            return true;                        // Give indication that we reached the server to leave.
        }

        internal void DeletePlayer(Player player)
        {
            if (null != player)
            {
                if (!string.IsNullOrEmpty(player.NickName))
                {
                    if (_Players.ContainsKey(player.NickName))
                    {
                        _Players.Remove(player.NickName);
                    }
                }
            }
        }

        internal FriendlyResult DeletePlayerImage(int playerId)
        {
            FriendlyResult ret = new FriendlyResult();

            string imageSourceUrl = string.Format(@"{0}\Player_{1}.jpg", Global.GameMgr.PlayerImagePath, playerId);

            if (!File.Exists(imageSourceUrl))
            {
                ret.FriendlyText = "Missing Player Image File: " + imageSourceUrl;
            }
            else
            {
                try
                {
                    File.Delete(imageSourceUrl);

                    ret.FriendlyText = string.Format("Deleted player image file #{0}.", playerId);

                    ret.ResultId = 1;
                }
                catch (Exception ex)
                {
                    ret.FriendlyText = string.Format("Error deleting player image file '{0}'.  {1}",
                        imageSourceUrl, ex.Message);
                }
            }

            return ret;
        }

        internal IEnumerable<Player> GamePlayers()
        {
            List<Player> ret = new List<Player>();

            Dictionary<string, Player>.Enumerator enumer = _Players.GetEnumerator();

            while (enumer.MoveNext())
            {
                ret.Add(enumer.Current.Value);
            }

            return ret;
        }

        internal IEnumerable<PlayerImage> GetPlayerImageList()
        {
            List<PlayerImage> ret = new List<PlayerImage>();

            string playerImagePath = Global.GameMgr.PlayerImagePath;
            string filePrefix = "Player_";

            string[] files = Directory.GetFiles(Global.GameMgr.PlayerImagePath, filePrefix + "*.jpg", SearchOption.TopDirectoryOnly);
            if (null != files &&
                files.Length > 0)
            {
                foreach (string file in files)
                {
                    FileInfo fi = new FileInfo(file);

                    ret.Add(new PlayerImage()
                    {
                        ImageSourceUrl = Global.GameMgr.PlayerImagePathUrl + "/" + fi.Name,

                        ImageFileName = fi.Name,
                        ImageSize = fi.Length
                    });
                }

                foreach (PlayerImage playerImage in ret)
                {
                    playerImage.ImageSourceUri = new Uri(playerImage.ImageSourceUrl, UriKind.RelativeOrAbsolute);
                }
            }

            return ret;
        }

        internal string[] GetNickNameSuggestions()
        {
            List<string> nickNameSuggestions = new List<string>(16);

            nickNameSuggestions.Add("Flash");
            nickNameSuggestions.Add("Silver");
            nickNameSuggestions.Add("Speed");
            nickNameSuggestions.Add("Reptar");
            nickNameSuggestions.Add("DaMan");
            nickNameSuggestions.Add("DaWoman");

            return nickNameSuggestions.ToArray();
        }

        #endregion                              // Internal Client Methods - Players

        #region Internal Client Methods - Questions

        internal IEnumerable<Question> Questions()
        {
            List<Question> ret = new List<Question>();

            Dictionary<int, Question>.Enumerator enumer = _Questions.GetEnumerator();

            while (enumer.MoveNext())
            {
                ret.Add(enumer.Current.Value);
            }

            return ret;
        }

        internal void AnswerQuestion(string playerNickName, int questionId, int answerId, out int correctAnswerId, out bool isFirstToAnswer)
        {
            correctAnswerId = 0;
            isFirstToAnswer = false;
            Question currentQuestion = CurrentQuestion;

            // Same Question
            if (_Players.Count > 0 &&                      // Players in the game
                null != _CurrentGame &&
                null != currentQuestion &&
                currentQuestion.QuestionId == questionId)
            {
                correctAnswerId = currentQuestion.CorrectAnswerId.Value;

                // Same user that submitted the question?
                if (currentQuestion.SubmittedByPlayerNickName != playerNickName)
                {
                    // Correct answer?
                    if (answerId == currentQuestion.CorrectAnswerId.Value)
                    {
                        // Previous correct answer or this is the first?
                        isFirstToAnswer = string.IsNullOrEmpty(currentQuestion.WonByPlayerNickName);
                        if (isFirstToAnswer)
                        {
                            Player player = GetPlayer(playerNickName);

                            if (!player.CurrentScore.HasValue)
                                player.CurrentScore = 0;

                            player.CurrentScore += 1;

                            currentQuestion.WonByPlayerId = player.PlayerId.Value;
                            currentQuestion.WonByPlayerNickName = player.NickName;

                            UpdateHighScore();

                            GameChanged();
                        }
                    }
                }
            }
        }

        private void UpdateHighScore()
        {
            int highScore = 0;
            string highScorePlayerNickName = string.Empty;

            GetHighScore(out highScore, out highScorePlayerNickName);

            CurrentGame.HighScore = highScore;
            CurrentGame.HighScorePlayerNickName = highScorePlayerNickName;
        }

        private void GetHighScore(out int highScore, out string highScorePlayerNickName)
        {
            highScore = 0;
            highScorePlayerNickName = string.Empty;

            if (null != _Players &&
                _Players.Count > 0)
            {
                Dictionary<string, Player>.Enumerator enumer = _Players.GetEnumerator();
                while (enumer.MoveNext())
                {
                    if (highScore < enumer.Current.Value.CurrentScore)
                    {
                        highScore = enumer.Current.Value.CurrentScore.Value;
                        highScorePlayerNickName = enumer.Current.Value.NickName;
                    }
                }
            }
        }

        private Player GetPlayer(string playerNickName)
        {
            Player ret = null;

            if (!string.IsNullOrEmpty(playerNickName) &&
                null != _Players &&
                _Players.Count > 0)
            {
                string loweredNickName = playerNickName.Trim().ToLowerInvariant();

                if (_Players.ContainsKey(loweredNickName))
                {
                    ret = _Players[loweredNickName];
                }
            }

            return ret;
        }

        private int GetPlayerId(string playerNickName)
        {
            int ret = 0;

            Player player = GetPlayer(playerNickName);

            if (null != player &&
                player.PlayerId.HasValue)
            {
                ret = player.PlayerId.Value;
            }

            return ret;
        }

        internal FriendlyResult SubmitQuestion(string userNickName, Question newQuestion)
        {
            FriendlyResult ret = null;

            if (string.IsNullOrEmpty(userNickName))
            {
                ret = new FriendlyResult() { ResultId = 0, FriendlyText = "Missing Nick Name." };
            }
            else if (null == newQuestion)
            {
                ret = new FriendlyResult() { ResultId = 0, FriendlyText = "Missing Question data." };
            }
            else
            {
                // Override the value for the actual name.
                newQuestion.SubmittedByPlayerNickName = userNickName;
                newQuestion.SubmittedTimeUTC = DateTime.UtcNow;

                ret = InsertUpdateQuestion(userNickName, newQuestion);
            }

            return ret;
        }

        internal IEnumerable<Question> GetQuestions()
        {
            List<Question> ret = new List<Question>();

            if (_Questions.Count > 0)
            {
                Dictionary<int, Question>.Enumerator enumer = _Questions.GetEnumerator();
                while (enumer.MoveNext())
                {
                    // Copy the value for return.
                    ret.Add(enumer.Current.Value);
                }
            }

            return ret.AsEnumerable<Question>();
        }

        internal FriendlyResult InsertUpdateQuestion(string nickName, Question question)
        {
            FriendlyResult ret = null;

            if (null == question)
            {
                ret = new FriendlyResult() { ResultId = 0, FriendlyText = "Missing Question data." };
            }
            if (string.IsNullOrEmpty(question.QuestionText))
            {
                ret = new FriendlyResult() { ResultId = 0, FriendlyText = "Missing Question data." };
            }

            // Calculate the id to determine a new or update.
            question.QuestionId = question.QuestionText.Trim().GetHashCode();

            if (!_Questions.ContainsKey(question.QuestionId))
            {
                ret = AddNewQuestionToPool(question);
            }
            else
            {
                Question existingQuestion = _Questions[question.QuestionId];

                if (existingQuestion.QuestionId != question.QuestionId)
                {
                    DeleteQuestion(question.QuestionId);

                    ret = AddNewQuestionToPool(question);
                }
                else                            // Update fields.
                {
                    existingQuestion.QuestionTypeId = question.QuestionTypeId;
                    existingQuestion.Answer1 = question.Answer1;
                    existingQuestion.Answer2 = question.Answer2;
                    existingQuestion.Answer3 = question.Answer3;
                    existingQuestion.Answer4 = question.Answer4;
                    existingQuestion.CorrectAnswerId = question.CorrectAnswerId;

                    ret = new FriendlyResult() { ResultId = 1, FriendlyText = "Successfully updated the question." };
                }
            }

            return ret;
        }

        internal void DeleteQuestion(int questionId)
        {
            if (_Questions.ContainsKey(questionId))
            {
                _Questions.Remove(questionId);
            }
        }

        // Result: 0-No question set, 1=The question was found and set.
        internal FriendlyResult SetCurrentQuestion(int questionId)
        {
            FriendlyResult ret = new FriendlyResult();

            if (null == _Questions ||
                _Questions.Count == 0)
            {
                ret.FriendlyText = "No questions have been loaded.";
            }
            else if (questionId == 0)        // All other hash codes are correct questions.
            {
                ret.FriendlyText = "Invalid question id specified.";
            }
            else if (!_Questions.ContainsKey(questionId))
            {
                ret.FriendlyText = "The specified question id does not exist.";
            }
            else
            {
                // Maintain the id, not the actual question.
                CurrentGame.CurrentQuestionId = questionId;
                GameChanged();

                ret.ResultId = questionId;
                ret.FriendlyText = "Successfully set the current question.";
            }

            return ret;
        }

        internal FriendlyResult SaveQuestions()
        {
            FriendlyResult ret = new FriendlyResult();

            if (null == _Questions ||
                _Questions.Count == 0)
            {
                ret = new FriendlyResult() { ResultId = 0, FriendlyText = "No questions to save." };
            }
            else
            {
                int saveCount = 0;
                try
                {
                    XElement xmlElementRoot = new XElement("Questions");
                    XDocument xDocument = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), xmlElementRoot);

                    Dictionary<int, Question>.Enumerator enumer = _Questions.GetEnumerator();
                    while (enumer.MoveNext())
                    {
                        Question question = enumer.Current.Value;

                        XElement xQuestion = new XElement("Question",
                            new XElement("QuestionId", question.QuestionId),
                            new XElement("QuestionTypeId", question.QuestionTypeId),
                            new XElement("QuestionText", question.QuestionText),
                            new XElement("Answer1", question.Answer1),
                            new XElement("Answer2", question.Answer2),
                            new XElement("Answer3", question.Answer3),
                            new XElement("Answer4", question.Answer4),
                            new XElement("CategoryName", question.CategoryName),
                            new XElement("CorrectAnswerId", question.CorrectAnswerId),
                            new XElement("SubmittedTimeUTC", question.SubmittedTimeUTC),
                            new XElement("SubmittedByPlayerNickName", question.SubmittedByPlayerNickName));

                        xmlElementRoot.Add(xQuestion);
                        saveCount++;
                    }


                    // Save to the App_Data folder.
                    xDocument.Save(Global.GameMgr.DataPath + "\\Questions_Other.xml");
                    ret = new FriendlyResult() { ResultId = saveCount, FriendlyText = "Questions saved to the server.  Total Saved: " + saveCount.ToString() };
                }
                catch (Exception ex)
                {
                    // Access denied if we don't set write permissions to the data folder.
                    // Show a friendly error instead of crashing.
                    ret = new FriendlyResult() { ResultId = saveCount, FriendlyText = "Error occurred during the save.  Reason: " + ex.Message };
                }
            }

            return ret;
        }

        internal int LoadQuestions()
        {
            int ret = 0;

            try
            {
                _Questions.Clear();

                XElement allData = XElement.Load(Global.GameMgr.DataPath + "\\Questions_Other.xml");
                if (allData != null)
                {
                    XElement xElement = null;

                    IEnumerable<XElement> xElementQuestions = allData.Descendants("Question");
                    foreach (XElement xElementQuestion in xElementQuestions)
                    {
                        Question newQuestion = new Question();

                        xElement = xElementQuestion.Element("QuestionId");
                        if (null != xElement)
                            newQuestion.QuestionId = Utils.ParseInt(xElement.Value);
                        xElement = xElementQuestion.Element("QuestionTypeId");
                        if (null != xElement)
                        {
                            QuestionType questionType = QuestionType.MultipleChoice;

                            if (Enum.TryParse<QuestionType>(xElement.Value, out questionType))
                                newQuestion.QuestionTypeId = questionType;
                        }
                        xElement = xElementQuestion.Element("QuestionText");
                        if (null != xElement)
                            newQuestion.QuestionText = xElement.Value;
                        xElement = xElementQuestion.Element("Answer1");
                        if (null != xElement)
                            newQuestion.Answer1 = xElement.Value;
                        xElement = xElementQuestion.Element("Answer2");
                        if (null != xElement)
                            newQuestion.Answer2 = xElement.Value;
                        xElement = xElementQuestion.Element("Answer3");
                        if (null != xElement)
                            newQuestion.Answer3 = xElement.Value;
                        xElement = xElementQuestion.Element("Answer4");
                        if (null != xElement)
                            newQuestion.Answer4 = xElement.Value;
                        xElement = xElementQuestion.Element("CategoryName");
                        if (null != xElement)
                            newQuestion.CategoryName = xElement.Value;
                        xElement = xElementQuestion.Element("SubmittedTimeUTC");
                        if (null != xElement)
                            newQuestion.SubmittedTimeUTC = Utils.ParseDate(xElement.Value);
                        xElement = xElementQuestion.Element("CorrectAnswerId");
                        if (null != xElement)
                            newQuestion.CorrectAnswerId = Utils.ParseInt(xElement.Value);
                        xElement = xElementQuestion.Element("SubmittedByPlayerNickName");
                        if (null != xElement)
                            newQuestion.SubmittedByPlayerNickName = xElement.Value;

                        InsertUpdateQuestion(newQuestion.SubmittedByPlayerNickName, newQuestion);
                    }
                }

                ret = _Questions.Count;
            }
            catch
            {
                // No file first time we load.
                // Access denied if we don't set write permissions to the data folder.
            }

            return ret;
        }
        #endregion                              // Internal Client Methods - Questions
    }
}